﻿// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)

using System;
using System.IO;

namespace Bravura.Native.PluginBase
{
	/// <summary>
	/// General-use constants, objects, and functions.
	/// </summary>
	public static class Utilities
	{
		/// <summary>
		/// Constant value of 2.0 * Math.PI.
		/// </summary>
		public static double TwoPi = 2.0 * Math.PI;

		/// <summary>
		/// A static System.Random instance.
		/// </summary>
		public static Random Random = new Random();

		/// <summary>
		/// Constructs a basic waveform using fourier synthesis.
		/// </summary>
		public static double[] BuildFourierBasicWaveform(int size, double decay, double phase, double combFreq, double combPhase)
		{
			int num = size / 2 + 1;
			Complex[] data = GenerateBasicWaveformData(num, decay, phase, combFreq, combPhase);
			double[] wf = Fourier.Synthesize(data, size, num);
			return wf;
		}

		/// <summary>
		/// Constructs a noise waveform using fourier synthesis.
		/// </summary>
		public static double[] BuildFourierNoiseWaveform(int size, double decay, int seed)
		{
			int num = size / 2 + 1;
			Complex[] data = GenerateNoiseWaveformData(num, decay, seed);
			double[] wf = Fourier.Synthesize(data, size, num);
			return wf;
		}

		/// <summary>
		/// Generates fourier data used to create a basic waveform.
		/// </summary>
		public static Complex[] GenerateBasicWaveformData(int num, double decay, double phase, double combFreq, double combPhase)
		{
			Complex[] data = new Complex[num];

			phase = TwoPi * phase;

			for (int i = 1; i < num; i++)
			{
				double amp = 1.0 / Math.Pow(i, decay);
				double comb = Math.Cos(TwoPi * (combFreq * i + combPhase));
				Polar p = new Polar { Mag = amp * comb, Ang = phase };
				data[i] = p.ToComplex();
			}

			return data;
		}

		/// <summary>
		/// Generates fourier data used to create a noise waveform.
		/// </summary>
		public static Complex[] GenerateNoiseWaveformData(int num, double decay, int seed)
		{
			Complex[] data = new Complex[num];

			Random rand = new Random(seed);

			for (int i = 1; i < num; i++)
			{
				double amp = 1.0 / Math.Pow(i, decay);
				Polar p = new Polar { Mag = amp * rand.NextDouble(), Ang = TwoPi * rand.NextDouble() };
				data[i] = p.ToComplex();
			}

			return data;
		}

		/// <summary>
		/// Loads a 16-bit mono raw sample.
		/// </summary>
		public static double[] LoadSample(FileInfo fileInfo, int channel, int channelCount, int skipBytes, int length)
		{
			// bytes per channel (always 2)
			int bpc = 2;

			// load the file
			byte[] bytes = File.ReadAllBytes(fileInfo.FullName);

			// allocate the buffer
			double[] temp = new double[length];

			// populate the buffer
			int from = bpc * channel + skipBytes;
			int skip = bpc * channelCount;
			for (int i = 0; i < length; i++)
			{
				temp[i] = BitConverter.ToInt16(bytes, from) / 32768.0;
				from += skip;
			}

			return temp;
		}

		/// <summary>
		/// Saves a 16-bit mono raw sample.
		/// </summary>
		public static void SaveSample(FileInfo fileInfo, double gain, double[] data)
		{
			FileStream fs = File.Create(fileInfo.FullName);
			int len = data.Length;
			for (int i = 0; i < len; i++)
			{
				double dval = gain * data[i];
				if (dval < -1.0) dval = -1.0;
				else
				if (dval > +1.0) dval = +1.0;
				Int16 val = (Int16)(32767.5 * dval);
				byte[] bytes = BitConverter.GetBytes(val);
				fs.Write(bytes, 0, 2);
			}
			fs.Flush();
			fs.Close();
		}

		/// <summary>
		/// Finds the next zero crossing.
		/// </summary>
		public static int FindZero(double[] input, int pos, int dir)
		{
			bool found = false;

			while(!found)
			{
				double v0 = input[pos - 1];
				double v1 = input[pos + 1];
				if (v0 <= 0 && v1 >= 0)
				{
					found = true;
				}
				else
				{
					pos = pos + dir;
				}
			}

			return pos;
		}

		/// <summary>
		/// Estimates the period of a particular position in a sample.
		/// </summary>
		public static int EstimatePeriod(double[] input, int pos, int matchSize, int loPer, int hiPer)
		{
			double[] match = new double[matchSize];
			int period = loPer;

			// get match samples
			for (int i = 0; i < matchSize; i++)
			{
				match[i] = input[pos + i];
			}

			// find best match for match samples from pos+loPer to pos+hiPer
			double best = double.MaxValue;
			for (int i = loPer; i < hiPer; i++)
			{
				// calculate match difference
				double diff = 0.0;
				for (int j = 0; j < matchSize; j++)
				{
					diff += Math.Abs(match[j] - input[pos + i + j]);
				}

				// see if we found a better match
				if (diff < best)
				{
					best = diff;
					period = i;
				}
			}

			return period;
		}

		/// <summary>
		/// Extracts a slice of a sample.
		/// </summary>
		public static double[] ExtractSlice(double[] input, int index, int len, double normalize)
		{
			double[] slice = new double[len];

			// find max amplitude within slice
			double max = 0.0;
			for (int i = 0; i < len; i++)
			{
				double val = Math.Abs(input[index + i]);
				if (val > max) max = val;
			}

			// find diff between beginning of this slice and next
			double zero0 = input[index];
			double zero1 = input[index + len];
			double slope = (zero1 - zero0) / (len);

			// copy waveform slice
			double k = (1.0 / max) * (normalize) + (1.0 / 1.0) * (1.0 - normalize);
			for (int i = 0; i < len; i++)
			{
				slice[i] = k * (input[index + i] - slope * i - zero0);
			}

			return slice;
		}

		/// <summary>
		/// Extracts waveform data from a slice of a sample.
		/// </summary>
		public static Complex[] ExtractWaveformData(double[] input, int index, int len, double normalize)
		{
			return Fourier.Analyze(ExtractSlice(input, index, len, normalize));
		}

		/// <summary>
		/// Takes the values and normalizes them such that their sum is 1.0.
		/// </summary>
		public static void Normalize(double[] values)
		{
			int n = values.Length;
			double s = 0.0;
			for (int i = 0; i < n; i++)
			{
				s += values[i];
			}
			double k = 1.0 / s;
			for (int i = 0; i < n; i++)
			{
				values[i] = k * values[i];
			}
		}
	}
}
